﻿// C:\Users\Jorge\Documents\Visual Studio 2010\Projects\C#
//   \LIB\SignalGenLib\SignalPlot.cs
//
// Windows form to plot signals.
//
// Programmer:  Jorge L. Orejel
//
// Last update: 03/01/2022 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

using Util_Lib;

namespace SignalGenLib
{
   public enum SignalContinuity { continuous, discontinuous };
   public enum SignalShape { sine, cosine, square, sawtooth, triangle, whiteNoise };

   public partial class SignalPlot : Form
   {
      private string description;          // Windows-form title.
      private SignalShape shape;
      private SignalContinuity continuity;
      private Bitmap bmp;                  // Bitmap to draw pixels.
      private int xAxis_Y,                 // Y coordinate of x-axis (middle of bmp.Height).
                  sigMin_Y,                // Minimum Y coordinate of a signal
                                           //    (has nothing to do with the actual signal value).
                  sigMax_Y;                // Maximum Y coordinate of a signal
                                           //    (has nothing to do with the actual signal value).
      private Graphics gr;
      private Font drawFont;
      private StringFormat drawFormat;
      private int iScale,                 // Scaling factor for plotting signals.
                  iScaleDIV2;
      private double dScale;              // {double} of {iScale}.

      private int nextParameter_Y;        // Y coordinate to draw {deltaY} 
                                          // and {timeStep} in function {DrawDelta_Y_X}.

      public SignalPlot( string _description, SignalShape _shape, 
                         SignalContinuity _continuity = SignalContinuity.continuous )
      {
         InitializeComponent();

         bmp = new Bitmap( pictureBox1.Width, pictureBox1.Height );

         pictureBox1.Image = bmp;
         gr = Graphics.FromImage( bmp );
         gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
         gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
         gr.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
         gr.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
         gr.Clear( Color.Transparent );

         drawFont = new System.Drawing.Font( "Calibri", 10,
                                             FontStyle.Regular, GraphicsUnit.Point );
         drawFormat = new StringFormat();
         description = _description;
         shape = _shape;
         continuity = _continuity;
         xAxis_Y = bmp.Height / 2; // Y coordinate of x-axis.
         iScale = 10;              // Arbitrary scaling factor.
         iScaleDIV2 = iScale / 2;
         dScale = (double)iScale;
      }// SignalPlot (constructor)

        public SignalPlot()
        {
        }

        /// <summary>Plot a list of {TimeMagnitude} points. 
        /// </summary>
        /// <param name="parameters">Parameters of the signal to be plotted.</param>
        /// <param name="list">List containing the (time, magnitude) points.</param>
        /// <param name="continuity">Continuity of the signal.
        /// </param>
        public void Plot( SignalParameters parameters, List<TimeMagnitude> list, 
                        SignalContinuity continuity = SignalContinuity.continuous )
      {
         int n, m;

         if ( list == null || ( n = list.Count ) == 0 )
         {
            MessageBox.Show( 
               String.Format( "No {0} values to plot", description ) );
         }
         else
         {
            int x, deltaX, currY, nextY;

            // Increasing signal-magnitude values are drawn from the
            // bottom of the {Bitmap} to its top.
            sigMax_Y = 0;
            sigMin_Y = bmp.Height;

            Draw_X_axis();
            Draw_Y_axis();

            drawFormat.FormatFlags = StringFormatFlags.DirectionRightToLeft;

            DrawParameters( parameters, shape );

            deltaX = this.Width / n;
            x = 0;
            m = n - 2;
            
            drawFormat.FormatFlags = StringFormatFlags.DirectionVertical;

            for ( int i = 0; i < n; ++i )
            {
               int iScaledMag = ScaledMagnitude( list[ i ], dScale );

               currY = xAxis_Y - iScaledMag;

               if ( currY > sigMax_Y )
               {
                  sigMax_Y = currY;
               }
               if ( currY < sigMin_Y )
               {
                  sigMin_Y = currY;
               }
               if ( x >= bmp.Width )
               {
                  break;
               }
               bmp.SetPixel( x, currY, Color.Black );
               
               if ( UtilFn.IsDivisible( list[ i ].Time, parameters.period ) )
               {
                  string label = String.Format( "___ {0:0.0000}", list[ i ].Time );
                  SizeF size = gr.MeasureString( label, drawFont );

                  gr.DrawString( label, drawFont, Brushes.Red, 
                                 new Point( x, bmp.Height - (int)size.Width ), 
                                 drawFormat );
               }              
               if ( continuity == SignalContinuity.discontinuous && i <= m )
               {
                  int iP1ScaledMag = ScaledMagnitude( list[ i + 1 ], dScale );

                  nextY = xAxis_Y - iP1ScaledMag;

                  if ( x > 0 && ( shape == SignalShape.square || shape == SignalShape.sawtooth ) )
                  {
                     if ( i < m )
                     {
                        CheckVerticalDiscontinuity( x, currY, nextY );
                     }
                     else // i == m
                     {
                        DrawVerticalDiscontinuity( x + deltaX, currY );
                     }
                  }
               }
               x += deltaX;
            }
            Draw_Y_axisNotches( parameters );
            this.ShowDialog();                // Display form in modal mode.
         }
      }// Plot

      private void Draw_X_axis()
      {
         for ( int x = 0; x < bmp.Width; ++x )
         {
            bmp.SetPixel( x, xAxis_Y, Color.Black );
         }
         string tLabel = "t (sec)";
         SizeF tLabelSize = gr.MeasureString( tLabel, drawFont );

         gr.DrawString( tLabel, drawFont,
                        Brushes.Red,
                        new Point( bmp.Width - (int)tLabelSize.Width, xAxis_Y - 15 ) );
      }// Draw_X_axis

      private void Draw_Y_axis()
      {
         for ( int y = 0; y < bmp.Height; ++y )
         {
            bmp.SetPixel( 0, y, Color.Black );
         }
      }// Draw_Y_axis

      /// <summary>Draw the parameters of a signal. 
      /// </summary>
      /// <param name="parameters">Parameters of the signal.</param>
      /// <param name="shape">Shape of the signal.
      /// </param>
      private void DrawParameters( SignalParameters parameters, SignalShape shape )
      {
         int x = 1, y = 1;

         string[] parameterStr 
                  = new string[] 
                        { 
                           String.Format( "Amplitude: {0:00.000} V; DC offset: {1:00.000} V", 
                                          parameters.amplitude, parameters.offset ),
                           String.Format( "Frequency: {0:0000.00} Hz; Period: {1:0.000} sec", 
                                          parameters.frequency, parameters.period )
                        };
         drawFormat.FormatFlags = StringFormatFlags.DirectionRightToLeft;

         for ( int i = 0; i < parameterStr.Length; ++i )
         {
            if ( i > 0 && shape == SignalShape.whiteNoise )
            {
               break; // White noise has neither frequency, nor period.
            }
            SizeF size = gr.MeasureString( parameterStr[ i ], drawFont );

            x = (int)size.Width + 2;

            Point point = new Point( x, y );

            gr.DrawString( parameterStr[ i ], drawFont, Brushes.Red, point, drawFormat );
            y += (int)size.Height;
            nextParameter_Y = y;
         }
      }// DrawParameters

      /// <summary>Scale the magnitude of a signal for plotting purposes.
      /// </summary>
      /// <param name="tm">Instance of a {TimeMagnitude} pair.</param>
      /// <param name="scale">Scale factor.</param>
      /// <returns>The scaled magnitude.
      /// </returns>
      private int ScaledMagnitude( TimeMagnitude tm, double scale )
      {
         return (int)( scale * tm.Magnitude );
      }// ScaledMagnitude

      /// <summary>Conditionally draw the discontinuity of a square or sawtooth signal.
      /// </summary>
      /// <param name="x">Position on the x axis (time).</param>
      /// <param name="currY">Current position in the y dimension (units of magnitude).</param>
      /// <param name="nextY">Next position in the y dimension (units of magnitude).
      /// </param>
      private void CheckVerticalDiscontinuity( int x, int currY, int nextY )
      {
         if ( x >= bmp.Width )
         {
            return;
         }
         int discLength = Math.Abs( currY - nextY );

         if ( discLength > iScaleDIV2 )
         {
            int y;

            if ( currY < nextY )
            {
               for ( y = currY; y <= nextY; ++y ) // Discontinuity going down.
               {
                  bmp.SetPixel( x, y, Color.Black );
               }
            }
            else // nextY < currY, Discontinuity going up.
            {
               for ( y = currY; y >= nextY; --y )
               {
                  bmp.SetPixel( x, y, Color.Black );
               }
            }
         }
      }// CheckVerticalDiscontinuity

      /// <summary>Draw the vertical discontinuity at the end of a square or sawtooth signal.
      /// </summary>
      /// <param name="x">Position on the x axis (time).</param>
      /// <param name="currY">Current position in the y dimension (units of magnitude).
      /// </param>
      private void DrawVerticalDiscontinuity( int x, int currY )
      {
         if ( x >= bmp.Width )
         {
            return;
         }
         int y;

         if ( currY < sigMax_Y )
         {
            for ( y = currY; y <= sigMax_Y; ++y )
            {
               bmp.SetPixel( x, y, Color.Black );
            }
         }
         else if ( currY > sigMin_Y )
         {
            for ( y = sigMin_Y; y <= currY; ++y )
            {
               bmp.SetPixel( x, y, Color.Black );
            }
         }
      }// DrawVerticalDiscontinuity

      /// <summary>Draw red notches on the Y axis. 
      /// </summary>
      /// <param name="parameters">Parameters of the signal.
      /// </param>
      private void Draw_Y_axisNotches( SignalParameters parameters )
      {
         int y, deltaY = 10;

         if ( xAxis_Y > sigMax_Y ) // Signal above the x-axis.
         {
            deltaY = ( xAxis_Y - sigMin_Y ) / 10;

            for ( y = sigMin_Y; y <= xAxis_Y; y+= deltaY )
            {
               DrawNotch( y );
            }
         }
         else if ( xAxis_Y < sigMin_Y ) // Signal below the x-axis.
         {
            deltaY = ( sigMax_Y - xAxis_Y ) / 10;

            for ( y = xAxis_Y; y <= sigMax_Y; y += deltaY )
            {
               DrawNotch( y );
            }
         }
         else // Signal crosses the x-axis.
         {
            deltaY = ( sigMax_Y - sigMin_Y ) / 10;

            for ( y = sigMin_Y; y <= sigMax_Y; y += deltaY )
            {
               DrawNotch( y );
            }
         }
         DrawDelta_Y_X( parameters );
      }// Draw_Y_axisNotches

      /// <summary>Draw a red notch at a certain position on the Y axis. 
      /// </summary>
      /// <param name="y">Position on the Y axis.
      /// </param>
      private void DrawNotch( int y )
      {
         for ( int x = 0; x < 10; ++x )
         {
            bmp.SetPixel( x, y, Color.Red );
         }
      }// DrawNotch      
      
      /// <summary>Draw the {amplitude} and the {timeStep} from the
      ///          parameters of a signal.
      /// </summary>
      /// <param name="parameters">Signal parameters.
      /// </param>
      private void DrawDelta_Y_X( SignalParameters parameters )
      {
         // There are 11 notches on the Y axis.

         string delta_Y_X_str = String.Format( "deltaY: {0:00.000} V, time step: {1:0.00000} sec",
                                               parameters.amplitude / 5.0, parameters.timeStep );

         drawFormat.FormatFlags = StringFormatFlags.DirectionRightToLeft;
         SizeF size = gr.MeasureString( delta_Y_X_str, drawFont );

         int x = (int)size.Width + 8;

         Point point = new Point( x, nextParameter_Y );

         gr.DrawString( delta_Y_X_str, drawFont, Brushes.Red, point, drawFormat );
      }// DrawDelta_Y_X
   }// SignalformPlot (class)
}// SignalGenLib (namespace)
